// --------------------------------------------------------------------------------------------------------------------
// <copyright file="CloudStorageAccountManager.cs" company="AzureContrib (Open source library http://azurecontrib.codeplex.com)">
//   Microsoft Public License (Ms-PL)
// </copyright>
// <summary>
//   Exception for errors when handling Windows Azure Cloud Drives.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;

using AzureContrib.WindowsAzure.Diagnostics;
using AzureContrib.WindowsAzure.StorageClient.Properties;

using Microsoft.WindowsAzure;

namespace AzureContrib.WindowsAzure.StorageClient
{
	/// <summary>
	/// A registry for cloud storage accounts used to abstract away specific accounts from the underlying code.
	/// </summary>
	/// <remarks><see cref="ICloudStorageAccountManager.AddCloudStorageAccount(string,ICloudStorageAccount)"/> is used during app start to register the application data contexts.</remarks>
	/// <remarks><see cref="ICloudStorageAccountManager.GetStorageAccount"/> is used in the app to fetch <see cref="ICloudStorageAccount"/>s given a certain context.</remarks>
	public class CloudStorageAccountManager : ICloudStorageAccountManager
	{
		private readonly ISettingsSource settingsSource;
		private IDictionary<string, ICloudStorageAccount> accountContexts;
		private ITrace trace;

		/// <summary>
		/// Initializes a new instance of the <see cref="CloudStorageAccountManager"/> class. 
		/// </summary>
		/// <param name="trace">
		/// The <see cref="ITrace"/> to use in the manager.
		/// </param>
		/// <param name="settingsSource">
		/// The current <see cref="ISettingsSource"/>.
		/// </param>
		public CloudStorageAccountManager(ITrace trace, ISettingsSource settingsSource)
		{
			this.trace = trace;
			this.settingsSource = settingsSource;
			accountContexts = new Dictionary<string, ICloudStorageAccount>();
		}

		/// <summary>
		/// Add a data context to a registry of such contexts <seealso cref="ICloudStorageAccountManager"/>.
		/// <seealso cref="ICloudStorageAccountManager.AddCloudStorageAccount(string,ICloudStorageAccount)"/>
		/// </summary>
		/// <param name="context">The name of the data context.</param>
		/// <param name="cloudStorageAccount">The way to access the account.</param>
		public void AddCloudStorageAccount(string context, ICloudStorageAccount cloudStorageAccount)
		{
			Assumes.NotNullOrEmpty(context, "context", Strings.ContextHasToBePresent);
			accountContexts.Add(context, cloudStorageAccount);
		}

		/// <summary>
		/// Add a data context to a registry of such contexts <seealso cref="ICloudStorageAccountManager"/>.
		/// <see cref="ICloudStorageAccountManager.AddCloudStorageAccount(string,string)"/>
		/// </summary>
		/// <param name="context">The name of the data context.</param>
		/// <param name="connectionString">The valid connection string to a <see cref="CloudStorageAccount"/></param>
		public void AddCloudStorageAccount(string context, string connectionString)
		{
			CloudStorageAccount account;
			if (!CloudStorageAccount.TryParse(connectionString, out account))
			{
				throw new ArgumentOutOfRangeException("connectionString", Strings.Invalid_CloudStorageAccount_ConnectionString);
			}
			
			AddCloudStorageAccount(context, account.Wrap());
		}

		/// <summary>
		/// Get a <see cref="ICloudStorageAccount"/> given a special named context.
		/// </summary>
		/// <param name="context">The named data context you use to abstract away from any particular <see cref="CloudStorageAccount"/></param>
		/// <returns>A <see cref="CloudStorageAccount"/> wrapped up in an <see cref="ICloudStorageAccount"/></returns>
		/// <remarks>If there is no context registered and the context name is a config settin this method will <see cref="CloudStorageAccount.TryParse"/> an account from that value.</remarks>
		/// <exception cref="KeyNotFoundException">If the <paramref name="context"/> is not a registered storage context. <seealso cref="AddCloudStorageAccount(string,ICloudStorageAccount)"/> and <seealso cref="AddCloudStorageAccount(string,string)"/>.</exception>
		public ICloudStorageAccount GetStorageAccount(string context)
		{
			Assumes.NotNullOrEmpty(context, "context", Strings.ContextHasToBePresent);

			if (!accountContexts.ContainsKey(context))
			{
				TryToLoadAccountUsingContextAsConfigKey(context);
			}

			if (!accountContexts.ContainsKey(context))
			{
				var message = Strings.UnableToRetrieveCloudStoreageAccountForContext.F(context);
				trace.Error(message);
				throw new KeyNotFoundException(message);
			}

			var cloudStorageAccount = accountContexts[context];

			return cloudStorageAccount;
		}

		private void TryToLoadAccountUsingContextAsConfigKey(string settingName)
		{
			string settingValue;
			if (!settingsSource.TryGetConfigurationSettingValue(settingName, out settingValue))
			{
				return;
			}

			CloudStorageAccount cloudStorageAccount;
			if (CloudStorageAccount.TryParse(settingValue, out cloudStorageAccount))
			{
				AddCloudStorageAccount(settingName, cloudStorageAccount.Wrap());
			}
		}
	}
}